Skip to content

Commit

Permalink
Support custom languages
Browse files Browse the repository at this point in the history
Add a "custom" language option toallow for customization of every
command and step in mono. This way you don't have to reuse another
language option and potentially running scripts not meant for that
language.

Allow custom package version scripts to be run so we don't need to
implement this for Python right now, while we figure out how we want it
to work.
  • Loading branch information
tombruijn committed Apr 25, 2023
1 parent 115e910 commit e712276
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 1 deletion.
24 changes: 24 additions & 0 deletions .changesets/support-custom-languages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
bump: "minor"
type: "add"
---

Support custom languages with the custom language option. Example custom language configuration:

```yaml
---
language: custom
repo: "https://github.com/appsignal/appsignal-python"
bootstrap:
command: "echo bootstrap"
clean:
command: "hatch clean"
build:
command: "hatch build"
publish:
command: "hatch publish"
test:
command: "hatch run test:pytest"
read_version: "hatch version"
write_version: "hatch version"
```
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ test:
- `language`
- Required.
- Supported values:
- `custom`, requires each step to be manually configured with a
[custom command](#customize-commands).
- `elixir`
- `nodejs`
- `ruby`
Expand Down Expand Up @@ -122,6 +124,29 @@ build:
post: echo I am run _after_ the build command
```

### Customize version updater

If the built-in version updater does not work for your project, or you're using
a "custom" language, you can customize the reading and updating of the package
version.

Specify the `read_version` and `write_version` config options to point to
scripts to execute.

- `read_version`: Executes a script that returns the current package version
via standard output.
- Example: `script/read_package_version`, is run as
`script/read_package_version` and returns `1.0.0`.
- `write_version`: Executes a script with the new version as the first
argument, that updates the version for the package.
- Example: `script/update_package_version`, is run as
`script/update_package_version 1.0.1`.

```yaml
read_version: script/read_package_version
write_version: script/update_package_version
```

## Commands

### Init
Expand Down
2 changes: 1 addition & 1 deletion lib/mono/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def execute_command(cmd, cmd_opts = {})
read, write = IO.pipe
cmd_options[[:out, :err]] = write
end
puts cmd
puts cmd if options.fetch(:print_command, true)
pid = Process.spawn(
options.fetch(:env, {}),
cmd,
Expand Down
2 changes: 2 additions & 0 deletions lib/mono/language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def self.for(language)
Languages::Elixir::Language
when "ruby"
Languages::Ruby::Language
when "custom"
Languages::Custom::Language
else
raise UnknownLanguageError, language
end
Expand Down
2 changes: 2 additions & 0 deletions lib/mono/languages.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

require "mono/language"
require "mono/languages/custom/language"
require "mono/languages/custom/package"
require "mono/languages/elixir/language"
require "mono/languages/elixir/package"
require "mono/languages/nodejs/client_helper"
Expand Down
21 changes: 21 additions & 0 deletions lib/mono/languages/custom/language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Mono
module Languages
module Custom
class Language < Language::Base
def bootstrap(_options = {})
# noop
end

def unbootstrap(_options = {})
# noop
end

def clean(_options = {})
# noop
end
end
end
end
end
44 changes: 44 additions & 0 deletions lib/mono/languages/custom/package.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Mono
module Languages
module Custom
class Package < PackageBase
def current_version
@current_version ||=
if config.config?("read_version")
version = run_command_in_package(
config.config("read_version"),
:capture => true,
:print_command => false
).strip
Version.parse(version)
else
raise NotImplementedError,
"Please add `read_version` config to `mono.yml` file."
end
end

# Not supported
def dependencies
[]
end

def update_spec
if config.config?("write_version")
run_command_in_package(
[
config.config("write_version"),
next_version
].join(" "),
:print_command => false
)
else
raise NotImplementedError,
"Please add `write_version` config to `mono.yml` file."
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/mono/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def self.for(language)
Languages::Elixir::Package
when "ruby"
Languages::Ruby::Package
when "custom"
Languages::Custom::Package
else
raise "Unknown language. Please configure `mono.yml` with a `language`."
end
Expand Down
94 changes: 94 additions & 0 deletions spec/lib/mono/cli/publish/custom_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

RSpec.describe Mono::Cli::Publish do
include PublishHelper

around { |example| with_mock_stdin { example.run } }

it "publishes the package" do
mono_config = {
"build" => { "command" => "echo build" },
"publish" => { "command" => "echo publish" },
"read_version" => "cat version.py",
"write_version" => "ruby write_version_file.rb"
}
prepare_custom_project mono_config do
create_version_file "1.2.3"
File.write("write_version_file.rb", %(File.write("version.py", ARGV[0])))
add_changeset :patch
end
confirm_publish_package
output = run_publish_process

project_dir = "/#{current_project}"
next_version = "1.2.4"

expect(output).to include(<<~OUTPUT), output
The following packages will be published (or not):
- #{current_project}:
Current version: v1.2.3
Next version: v1.2.4 (patch)
OUTPUT
expect(output).to include(<<~OUTPUT), output
# Updating package versions
- #{current_project}:
Current version: v1.2.3
Next version: v1.2.4 (patch)
OUTPUT

in_project do
expect(File.read("version.py")).to include(next_version)
expect(current_package_changeset_files.length).to eql(0)

changelog = File.read("CHANGELOG.md")
expect_changelog_to_include_version_header(changelog, next_version)
expect_changelog_to_include_release_notes(changelog, :patch)

expect(local_changes?).to be_falsy, local_changes.inspect
expect(commited_files).to eql([
".changesets/1_patch.md",
"CHANGELOG.md",
"version.py"
])
end

expect(performed_commands).to eql([
[project_dir, "cat version.py"],
[project_dir, "ruby write_version_file.rb #{next_version}"],
[project_dir, "echo build"],
[project_dir, "git add -A"],
[project_dir, "git commit -m 'Publish packages' -m '- v#{next_version}' -m '[ci skip]'"],
[project_dir, "git tag v#{next_version}"],
[project_dir, "echo publish"],
[project_dir, "git push origin main v#{next_version}"]
])
expect(exit_status).to eql(0), output
end

def prepare_custom_project(config = {})
prepare_new_project do
create_mono_config({ "language" => "custom" }.merge(config))
yield
end
end

def create_version_file(version)
File.write("version.py", version)
end

def run_publish_process(failed_commands: [], stubbed_commands: [/^git push/])
output =
capture_stdout do
in_project do
perform_commands do
fail_commands failed_commands do
stub_commands stubbed_commands do
run_publish
end
end
end
end
end
strip_changeset_output output
end
end

0 comments on commit e712276

Please sign in to comment.