diff --git a/.gitignore b/.gitignore index 7a0bbfc..52be45d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Emacs backup files +*~ + # Bazel Ignores **/bazel-* .bazelrc.user diff --git a/ruby/private/gem.bzl b/ruby/private/gem.bzl index d94bb50..9d43288 100644 --- a/ruby/private/gem.bzl +++ b/ruby/private/gem.bzl @@ -11,12 +11,14 @@ def rb_gem(name, version, gem_name, srcs = [], **kwargs): _gemspec_name = name + "_gemspec" deps = kwargs.get("deps", []) source_date_epoch = kwargs.pop("source_date_epoch", None) + strip_package = kwargs.pop("strip_package", "") verbose = kwargs.pop("verbose", False) _rb_gemspec( name = _gemspec_name, gem_name = gem_name, version = version, + strip_package = strip_package, **kwargs ) @@ -28,5 +30,6 @@ def rb_gem(name, version, gem_name, srcs = [], **kwargs): deps = srcs + deps, visibility = ["//visibility:public"], source_date_epoch = source_date_epoch, + strip_package = strip_package, verbose = verbose, ) diff --git a/ruby/private/gem/dest_path.bzl b/ruby/private/gem/dest_path.bzl new file mode 100644 index 0000000..351f11b --- /dev/null +++ b/ruby/private/gem/dest_path.bzl @@ -0,0 +1,5 @@ +def dest_path(f, pkg): + result = f.short_path + if pkg and result.startswith(pkg): + result = result[1+len(pkg):] + return result diff --git a/ruby/private/gem/gem.bzl b/ruby/private/gem/gem.bzl index 2bf48d3..8701833 100644 --- a/ruby/private/gem/gem.bzl +++ b/ruby/private/gem/gem.bzl @@ -1,4 +1,5 @@ load("//ruby/private:providers.bzl", "RubyGem") +load("//ruby/private/gem:dest_path.bzl", _dest_path = "dest_path") # Runs gem with arbitrary arguments # eg: run_gem(runtime_ctx, ["install" "foo"]) @@ -8,15 +9,21 @@ def _rb_build_gem_impl(ctx): _inputs = [ctx.file._gem_runner, metadata_file, gemspec] _srcs = [] + + strip_package = ctx.attr.strip_package + for dep in ctx.attr.deps: file_deps = dep.files.to_list() _inputs.extend(file_deps) for f in file_deps: + dest_path = _dest_path(f, strip_package) _srcs.append({ "src_path": f.path, - "dest_path": f.short_path, + "dest_path": dest_path, }) + do_strip = (strip_package != "") + ctx.actions.write( output = metadata_file, content = struct( @@ -25,6 +32,7 @@ def _rb_build_gem_impl(ctx): output_path = ctx.outputs.gem.path, source_date_epoch = ctx.attr.source_date_epoch, verbose = ctx.attr.verbose, + do_strip = do_strip, ).to_json(), ) @@ -56,7 +64,7 @@ _ATTRS = { cfg = "host", ), "_gem_runner": attr.label( - default = Label("@coinbase_rules_ruby//ruby/private/gem:gem_runner.rb"), + default = ":gem_runner.rb", allow_single_file = True, ), "gemspec": attr.label( @@ -71,6 +79,10 @@ _ATTRS = { "source_date_epoch": attr.string( doc = "Sets source_date_epoch env var which should make output gems hermetic", ), + "strip_package": attr.string( + default = "", + doc = "strip this dir prefix from file paths added to the gem, such as package_name()", + ), "verbose": attr.bool(default = False), } diff --git a/ruby/private/gem/gem_runner.rb b/ruby/private/gem/gem_runner.rb index 80cf892..a958ea9 100644 --- a/ruby/private/gem/gem_runner.rb +++ b/ruby/private/gem/gem_runner.rb @@ -31,12 +31,24 @@ def parse_opts metadata_file end -def copy_srcs(dir, srcs, verbose) +def copy_srcs(dir, srcs, verbose, do_strip) # Sources need to be moved from their bazel_out locations # to the correct folder in the ruby gem. srcs.each do |src| src_path = src['src_path'] dest_path = src['dest_path'] + + if do_strip and File.directory?(src_path) + # Lop off the leading path element. + # If that was the only path element, + # copy the source dir's contents to the dest, + # rather than the source itself. + dest_path = dest_path.split('/', 2)[1..].join('/') + if dest_path == '' + src_path += '/.' + end + end + tmpname = File.join(dir, File.dirname(dest_path)) FileUtils.mkdir_p(tmpname) puts "copying #{src_path} to #{tmpname}" if verbose @@ -80,8 +92,9 @@ def build_gem(metadata) # We copy all related files to a tmpdir, build the entire gem in that tmpdir # and then copy the output gem into the correct bazel output location. verbose = metadata['verbose'] + do_strip = metadata['do_strip'] Dir.mktmpdir do |dir| - copy_srcs(dir, metadata['srcs'], verbose) + copy_srcs(dir, metadata['srcs'], verbose, do_strip) copy_gemspec(dir, metadata['gemspec_path']) do_build(dir, metadata['gemspec_path'], metadata['output_path']) end diff --git a/ruby/private/gem/gemspec.bzl b/ruby/private/gem/gemspec.bzl index 97ff558..64018e2 100644 --- a/ruby/private/gem/gemspec.bzl +++ b/ruby/private/gem/gemspec.bzl @@ -7,6 +7,7 @@ load( "RubyGem", "RubyLibrary", ) +load("//ruby/private/gem:dest_path.bzl", _dest_path = "dest_path") def _get_transitive_srcs(srcs, deps): return depset( @@ -14,19 +15,23 @@ def _get_transitive_srcs(srcs, deps): transitive = [dep[RubyLibrary].transitive_ruby_srcs for dep in deps], ) -def _rb_gem_impl(ctx): +def _rb_gemspec_impl(ctx): gemspec = ctx.actions.declare_file("{}.gemspec".format(ctx.attr.gem_name)) metadata_file = ctx.actions.declare_file("{}_metadata".format(ctx.attr.gem_name)) _ruby_files = [] file_deps = _get_transitive_srcs([], ctx.attr.deps).to_list() + + strip_package = ctx.attr.strip_package + for f in file_deps: # For some files the src_path and dest_path will be the same, but - # for othrs the src_path will be in bazel)out while the dest_path + # for others the src_path will be in bazel-out while the dest_path # will be from the workspace root. + dest_path = _dest_path(f, strip_package) _ruby_files.append({ "src_path": f.path, - "dest_path": f.short_path, + "dest_path": dest_path, }) ctx.actions.write( @@ -39,6 +44,7 @@ def _rb_gem_impl(ctx): licenses = ctx.attr.licenses, require_paths = ctx.attr.require_paths, gem_runtime_dependencies = ctx.attr.gem_runtime_dependencies, + do_strip = (strip_package != ""), ).to_json(), ) @@ -101,7 +107,7 @@ _ATTRS = { "require_paths": attr.string_list(), "_gemspec_template": attr.label( allow_single_file = True, - default = "gemspec_template.tpl", + default = ":gemspec_template.tpl", ), "ruby_sdk": attr.string( default = "@org_ruby_lang_ruby_toolchain", @@ -113,13 +119,17 @@ _ATTRS = { cfg = "host", ), "_gemspec_builder": attr.label( - default = Label("@coinbase_rules_ruby//ruby/private/gem:gemspec_builder.rb"), + default = ":gemspec_builder.rb", allow_single_file = True, ), + "strip_package": attr.string( + default = "", + doc = "strip this dir prefix from file paths added to the gem, such as package_name()", + ), } rb_gemspec = rule( - implementation = _rb_gem_impl, + implementation = _rb_gemspec_impl, attrs = _ATTRS, provides = [DefaultInfo, RubyGem], ) diff --git a/ruby/private/gem/gemspec_builder.rb b/ruby/private/gem/gemspec_builder.rb index 20e1e50..ede7c29 100644 --- a/ruby/private/gem/gemspec_builder.rb +++ b/ruby/private/gem/gemspec_builder.rb @@ -63,20 +63,32 @@ def expand_src_dirs(metadata) # Files and required paths can include a directory which gemspec # cannot handle. This will convert directories to individual files srcs = metadata['raw_srcs'] + do_strip = metadata['do_strip'] + new_srcs = [] + dests = [] srcs.each do |src| src_path = src['src_path'] dest_path = src['dest_path'] if File.directory?(src_path) Dir.glob("#{src_path}/**/*") do |f| # expand the directory, replacing each src path with its dest path - new_srcs << f.gsub(src_path, dest_path) if File.file?(f) + if File.file?(f) + g = f.gsub(src_path, dest_path) + new_srcs << g + if do_strip + dests << g.sub(/^[^\/]+\//, '') + else + dests << g + end + end end elsif File.file?(src_path) new_srcs << dest_path end end metadata['srcs'] = new_srcs + metadata['dests'] = dests metadata end diff --git a/ruby/private/gem/gemspec_template.tpl b/ruby/private/gem/gemspec_template.tpl index 2a101c5..40fab57 100644 --- a/ruby/private/gem/gemspec_template.tpl +++ b/ruby/private/gem/gemspec_template.tpl @@ -4,7 +4,7 @@ Gem::Specification.new do |s| s.authors = {authors} s.version = "{version}" s.licenses = {licenses} - s.files = {srcs} + s.files = {dests} s.require_paths = {require_paths} {gem_runtime_dependencies}