diff --git a/tools/migration/BUILD b/tools/migration/BUILD new file mode 100644 index 00000000..06b4f278 --- /dev/null +++ b/tools/migration/BUILD @@ -0,0 +1,45 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +py_binary( + name = "legacy_fields_migrator", + srcs = ["legacy_fields_migrator.py"], + deps = [ + ":legacy_fields_migration_lib", + "//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2", + "@io_abseil_py//absl:app", + "@io_abseil_py//absl/flags", + ], +) + +py_library( + name = "legacy_fields_migration_lib", + srcs = ["legacy_fields_migration_lib.py"], + deps = [ + "//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2", + ], +) + +py_test( + name = "legacy_fields_migration_lib_test", + srcs = ["legacy_fields_migration_lib_test.py"], + deps = [ + ":legacy_fields_migration_lib", + "//third_party/com/github/bazelbuild/bazel/src/main/protobuf:crosstool_config_py_pb2", + ], +) diff --git a/tools/migration/legacy_fields_migration_lib.py b/tools/migration/legacy_fields_migration_lib.py new file mode 100644 index 00000000..ac0ae8d4 --- /dev/null +++ b/tools/migration/legacy_fields_migration_lib.py @@ -0,0 +1,217 @@ +"""Module providing migrate_legacy_fields function. + +migrate_legacy_fields takes parsed CROSSTOOL proto and migrates it (inplace) to +use only the features. + +Tracking issue: https://github.com/bazelbuild/bazel/issues/5187 + +Since C++ rules team is working on migrating CROSSTOOL from text proto into +Starlark, we advise CROSSTOOL owners to wait for the CROSSTOOL -> Starlark +migrator before they invest too much time into fixing their pipeline. Tracking +issue for the Starlark effort is +https://github.com/bazelbuild/bazel/issues/5380. +""" + +from third_party.com.github.bazelbuild.bazel.src.main.protobuf import crosstool_config_pb2 + +ALL_COMPILE_ACTIONS = [ + "assemble", "preprocess-assemble", "linkstamp-compile", "c-compile", + "c++-compile", "c++-header-parsing", "c++-module-compile", + "c++-module-codegen", "lto-backend", "clif-match" +] + +ALL_CXX_COMPILE_ACTIONS = [ + action for action in ALL_COMPILE_ACTIONS if action != "c-compile" +] + +ALL_LINK_ACTIONS = [ + "c++-link-executable", "c++-link-dynamic-library", + "c++-link-nodeps-dynamic-library" +] + +# Map converting from LinkingMode to corresponding feature name +LINKING_MODE_TO_FEATURE_NAME = { + "FULLY_STATIC": "fully_static_link", + "MOSTLY_STATIC": "static_linking_mode", + "DYNAMIC": "dynamic_linking_mode", + "MOSTLY_STATIC_LIBRARIES": "static_linking_mode_nodeps_library", +} + + +def migrate_legacy_fields(crosstool): + """Migrates parsed crosstool (inplace) to not use legacy fields.""" + crosstool.ClearField("default_toolchain") + for toolchain in crosstool.toolchain: + # clear noop fields first + toolchain.ClearField("debian_extra_requires") + toolchain.ClearField("gcc_plugin_compiler_flag") + toolchain.ClearField("ar_flag") + toolchain.ClearField("ar_thin_archives_flag") + toolchain.ClearField("gcc_plugin_header_directory") + toolchain.ClearField("mao_plugin_header_directory") + toolchain.ClearField("supports_normalizing_ar") + toolchain.ClearField("supports_thin_archives") + toolchain.ClearField("supports_incremental_linker") + toolchain.ClearField("supports_dsym") + toolchain.ClearField("default_python_top") + toolchain.ClearField("default_python_version") + toolchain.ClearField("python_preload_swigdeps") + + _ = [_migrate_expand_if_all_available(f) for f in toolchain.feature] + _ = [_migrate_expand_if_all_available(ac) for ac in toolchain.action_config] + + # Create default_link_flags feature for linker_flag + flag_sets = _extract_legacy_link_flag_sets_for(toolchain) + if flag_sets: + if _contains_feature(toolchain, "default_link_flags"): + continue + feature = _prepend_feature(toolchain) + feature.name = "default_link_flags" + feature.enabled = True + _add_flag_sets(feature, flag_sets) + + # Create default_compile_flags feature for compiler_flag, cxx_flag + flag_sets = _extract_legacy_compile_flag_sets_for(toolchain) + if flag_sets and not _contains_feature(toolchain, "default_compile_flags"): + feature = _prepend_feature(toolchain) + feature.enabled = True + feature.name = "default_compile_flags" + _add_flag_sets(feature, flag_sets) + + toolchain.ClearField("compilation_mode_flags") + + # Unfiltered cxx flags have to have their own special feature. + # "unfiltered_compile_flags" is a well-known (by Bazel) feature name that is + # excluded from nocopts filtering. + if toolchain.unfiltered_cxx_flag: + # If there already is a feature named unfiltered_compile_flags, the + # crosstool is already migrated for unfiltered_compile_flags + if _contains_feature(toolchain, "unfiltered_compile_flags"): + for f in toolchain.feature: + if f.name == "unfiltered_compile_flags": + feature = f + break + else: + feature = toolchain.feature.add() + feature.name = "unfiltered_compile_flags" + feature.enabled = True + + _add_flag_sets( + feature, [[None, ALL_COMPILE_ACTIONS, toolchain.unfiltered_cxx_flag]]) + toolchain.ClearField("unfiltered_cxx_flag") + + +def _add_flag_sets(feature, flag_sets): + """Add flag sets into a feature.""" + for flag_set in flag_sets: + with_feature = flag_set[0] + actions = flag_set[1] + flags = flag_set[2] + flag_set = feature.flag_set.add() + if with_feature is not None: + flag_set.with_feature.add().feature[:] = [with_feature] + flag_set.action[:] = actions + flag_group = flag_set.flag_group.add() + flag_group.flag[:] = flags + return feature + + +def _extract_legacy_compile_flag_sets_for(toolchain): + """Get flag sets for default_compile_flags feature.""" + result = [] + if toolchain.compiler_flag: + result.append([None, ALL_COMPILE_ACTIONS, toolchain.compiler_flag]) + toolchain.ClearField("compiler_flag") + if toolchain.cxx_flag: + result.append([None, ALL_CXX_COMPILE_ACTIONS, toolchain.cxx_flag]) + toolchain.ClearField("cxx_flag") + + # Migrate compiler_flag/cxx_flag from compilation_mode_flags + for cmf in toolchain.compilation_mode_flags: + mode = crosstool_config_pb2.CompilationMode.Name(cmf.mode).lower() + # coverage mode has been a noop since a while + if mode == "coverage": + continue + + if (cmf.compiler_flag or + cmf.cxx_flag) and not _contains_feature(toolchain, mode): + feature = toolchain.feature.add() + feature.name = mode + + if cmf.compiler_flag: + result.append([mode, ALL_COMPILE_ACTIONS, cmf.compiler_flag]) + + if cmf.cxx_flag: + result.append([mode, ALL_CXX_COMPILE_ACTIONS, cmf.cxx_flag]) + + return result + + +def _extract_legacy_link_flag_sets_for(toolchain): + """Get flag sets for default_link_flags feature.""" + result = [] + + # Migrate linker_flag + if toolchain.linker_flag: + result.append([None, ALL_LINK_ACTIONS, toolchain.linker_flag]) + toolchain.ClearField("linker_flag") + + # Migrate linker_flags from compilation_mode_flags + for cmf in toolchain.compilation_mode_flags: + mode = crosstool_config_pb2.CompilationMode.Name(cmf.mode).lower() + # coverage mode has beed a noop since a while + if mode == "coverage": + continue + + if cmf.linker_flag and not _contains_feature(toolchain, mode): + feature = toolchain.feature.add() + feature.name = mode + + if cmf.linker_flag: + result.append([mode, ALL_LINK_ACTIONS, cmf.linker_flag]) + + # Migrate linker_flags from linking_mode_flags + for lmf in toolchain.linking_mode_flags: + mode = crosstool_config_pb2.LinkingMode.Name(lmf.mode) + mode = LINKING_MODE_TO_FEATURE_NAME.get(mode) + # if the feature is already there, we don't migrate, lmf is not used + if _contains_feature(toolchain, mode): + continue + + # dynamic_linking_mode is also a marker feature it has a meaning + # even when empty + if lmf.linker_flag or mode == "dynamic_linking_mode": + feature = toolchain.feature.add() + feature.name = mode + + if lmf.linker_flag: + result.append([mode, ALL_LINK_ACTIONS, lmf.linker_flag]) + toolchain.ClearField("linking_mode_flags") + + return result + + +def _prepend_feature(toolchain): + """Create a new feature and make it be the first in the toolchain.""" + features = toolchain.feature + toolchain.ClearField("feature") + new_feature = toolchain.feature.add() + toolchain.feature.extend(features) + return new_feature + + +def _contains_feature(toolchain, name): + """Returns True when toolchain contains a feature with a given name.""" + return any(feature.name == name for feature in toolchain.feature) + + +def _migrate_expand_if_all_available(message): + """Move expand_if_all_available fields to flag_groups.""" + for flag_set in message.flag_set: + if flag_set.expand_if_all_available: + for flag_group in flag_set.flag_group: + new_vars = ( + flag_group.expand_if_all_available[:] + + flag_set.expand_if_all_available[:]) + flag_group.expand_if_all_available[:] = new_vars + flag_set.ClearField("expand_if_all_available") diff --git a/tools/migration/legacy_fields_migration_lib_test.py b/tools/migration/legacy_fields_migration_lib_test.py new file mode 100644 index 00000000..c0165ce1 --- /dev/null +++ b/tools/migration/legacy_fields_migration_lib_test.py @@ -0,0 +1,422 @@ +import unittest +from google.protobuf import text_format +from third_party.com.github.bazelbuild.bazel.src.main.protobuf import crosstool_config_pb2 +from tools.migration.legacy_fields_migration_lib import ALL_COMPILE_ACTIONS +from tools.migration.legacy_fields_migration_lib import ALL_CXX_COMPILE_ACTIONS +from tools.migration.legacy_fields_migration_lib import ALL_LINK_ACTIONS +from tools.migration.legacy_fields_migration_lib import migrate_legacy_fields + + +def assert_has_feature(self, toolchain, name): + self.assertTrue(any(feature.name == name for feature in toolchain.feature)) + + +def make_crosstool(string): + crosstool = crosstool_config_pb2.CrosstoolRelease() + text_format.Merge("major_version: '123' minor_version: '456'", crosstool) + toolchain = crosstool.toolchain.add() + text_format.Merge(string, toolchain) + return crosstool + + +def migrate_to_string(crosstool): + migrate_legacy_fields(crosstool) + return to_string(crosstool) + + +def to_string(crosstool): + return text_format.MessageToString(crosstool) + + +class LegacyFieldsMigrationLibTest(unittest.TestCase): + + def test_deletes_unused_fields(self): + crosstool = make_crosstool(""" + debian_extra_requires: 'debian-1' + gcc_plugin_compiler_flag: 'gcc_plugin_compiler_flag-1' + ar_flag: 'ar_flag-1' + ar_thin_archives_flag: 'ar_thin_archives_flag-1' + gcc_plugin_header_directory: 'gcc_plugin_header_directory-1' + mao_plugin_header_directory: 'mao_plugin_header_directory-1' + default_python_top: 'default_python_top-1' + default_python_version: 'default_python_version-1' + python_preload_swigdeps: false + supports_normalizing_ar: false + supports_thin_archives: false + supports_incremental_linker: false + supports_dsym: false + """) + output = migrate_to_string(crosstool) + self.assertNotIn("debian_extra_requires", output) + self.assertNotIn("gcc_plugin_compiler_flag", output) + self.assertNotIn("ar_flag", output) + self.assertNotIn("ar_thin_archives_flag", output) + self.assertNotIn("gcc_plugin_header_directory", output) + self.assertNotIn("mao_plugin_header_directory", output) + self.assertNotIn("supports_normalizing_ar", output) + self.assertNotIn("supports_thin_archives", output) + self.assertNotIn("supports_incremental_linker", output) + self.assertNotIn("supports_dsym", output) + self.assertNotIn("default_python_top", output) + self.assertNotIn("default_python_version", output) + self.assertNotIn("python_preload_swigdeps", output) + + def test_deletes_default_toolchains(self): + crosstool = make_crosstool("") + crosstool.default_toolchain.add() + self.assertEqual(len(crosstool.default_toolchain), 1) + migrate_legacy_fields(crosstool) + self.assertEqual(len(crosstool.default_toolchain), 0) + + def test_migrate_compiler_flags(self): + crosstool = make_crosstool(""" + compiler_flag: 'clang-flag-1' + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(len(output.compiler_flag), 0) + self.assertEqual(output.feature[0].name, "default_compile_flags") + self.assertEqual(output.feature[0].flag_set[0].action, ALL_COMPILE_ACTIONS) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["clang-flag-1"]) + + def test_migrate_cxx_flags(self): + crosstool = make_crosstool(""" + cxx_flag: 'clang-flag-1' + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(len(output.cxx_flag), 0) + self.assertEqual(output.feature[0].name, "default_compile_flags") + self.assertEqual(output.feature[0].flag_set[0].action, + ALL_CXX_COMPILE_ACTIONS) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["clang-flag-1"]) + + def test_compiler_flag_come_before_cxx_flags(self): + crosstool = make_crosstool(""" + compiler_flag: 'clang-flag-1' + cxx_flag: 'clang-flag-2' + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "default_compile_flags") + self.assertEqual(output.feature[0].flag_set[0].action, ALL_COMPILE_ACTIONS) + self.assertEqual(output.feature[0].flag_set[1].action, + ALL_CXX_COMPILE_ACTIONS) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["clang-flag-1"]) + self.assertEqual(output.feature[0].flag_set[1].flag_group[0].flag, + ["clang-flag-2"]) + + def test_migrate_linker_flags(self): + crosstool = make_crosstool(""" + linker_flag: 'linker-flag-1' + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(len(output.linker_flag), 0) + self.assertEqual(output.feature[0].name, "default_link_flags") + self.assertEqual(output.feature[0].flag_set[0].action, ALL_LINK_ACTIONS) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["linker-flag-1"]) + + def test_compilation_mode_flags(self): + crosstool = make_crosstool(""" + compiler_flag: "compile-flag-1" + cxx_flag: "cxx-flag-1" + linker_flag: "linker-flag-1" + compilation_mode_flags { + mode: OPT + compiler_flag: "opt-flag-1" + cxx_flag: "opt-flag-2" + linker_flag: "opt-flag-3" + } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(len(output.compilation_mode_flags), 0) + assert_has_feature(self, output, "opt") + + self.assertEqual(output.feature[0].name, "default_compile_flags") + self.assertEqual(output.feature[1].name, "default_link_flags") + + # flag set for compiler_flag fields + self.assertEqual(len(output.feature[0].flag_set[0].with_feature), 0) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["compile-flag-1"]) + + # flag set for cxx_flag fields + self.assertEqual(len(output.feature[0].flag_set[1].with_feature), 0) + self.assertEqual(output.feature[0].flag_set[1].flag_group[0].flag, + ["cxx-flag-1"]) + + # flag set for compiler_flag from compilation_mode_flags + self.assertEqual(len(output.feature[0].flag_set[2].with_feature), 1) + self.assertEqual(output.feature[0].flag_set[2].with_feature[0].feature[0], + "opt") + self.assertEqual(output.feature[0].flag_set[2].flag_group[0].flag, + ["opt-flag-1"]) + + # flag set for cxx_flag from compilation_mode_flags + self.assertEqual(len(output.feature[0].flag_set[3].with_feature), 1) + self.assertEqual(output.feature[0].flag_set[3].with_feature[0].feature[0], + "opt") + self.assertEqual(output.feature[0].flag_set[3].flag_group[0].flag, + ["opt-flag-2"]) + + # default_link_flags, flag set for linker_flag + self.assertEqual(len(output.feature[1].flag_set[0].with_feature), 0) + self.assertEqual(output.feature[1].flag_set[0].flag_group[0].flag, + ["linker-flag-1"]) + + # default_link_flags, flag set for linker_flag from + # compilation_mode_flags + self.assertEqual(len(output.feature[1].flag_set[1].with_feature), 1) + self.assertEqual(output.feature[1].flag_set[1].with_feature[0].feature[0], + "opt") + self.assertEqual(output.feature[1].flag_set[1].flag_group[0].flag, + ["opt-flag-3"]) + + def test_linking_mode_flags(self): + crosstool = make_crosstool(""" + linker_flag: "linker-flag-1" + compilation_mode_flags { + mode: DBG + linker_flag: "dbg-flag-1" + } + linking_mode_flags { + mode: MOSTLY_STATIC + linker_flag: "mostly-static-flag-1" + } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(len(output.compilation_mode_flags), 0) + self.assertEqual(len(output.linking_mode_flags), 0) + + # flag set for linker_flag + self.assertEqual(len(output.feature[0].flag_set[0].with_feature), 0) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["linker-flag-1"]) + + # flag set for compilation_mode_flags + self.assertEqual(len(output.feature[0].flag_set[1].with_feature), 1) + self.assertEqual(output.feature[0].flag_set[1].with_feature[0].feature[0], + "dbg") + self.assertEqual(output.feature[0].flag_set[1].flag_group[0].flag, + ["dbg-flag-1"]) + + # flag set for linking_mode_flags + self.assertEqual(len(output.feature[0].flag_set[2].with_feature), 1) + self.assertEqual(output.feature[0].flag_set[2].with_feature[0].feature[0], + "static_linking_mode") + self.assertEqual(output.feature[0].flag_set[2].flag_group[0].flag, + ["mostly-static-flag-1"]) + + def test_coverage_compilation_mode_ignored(self): + crosstool = make_crosstool(""" + compilation_mode_flags { + mode: COVERAGE + compiler_flag: "coverage-flag-1" + cxx_flag: "coverage-flag-2" + linker_flag: "coverage-flag-3" + } + """) + output = migrate_to_string(crosstool) + self.assertNotIn("compilation_mode_flags", output) + self.assertNotIn("coverage-flag-1", output) + self.assertNotIn("coverage-flag-2", output) + self.assertNotIn("coverage-flag-3", output) + self.assertNotIn("COVERAGE", output) + + def test_dynamic_linking_mode_is_added_even_when_empty(self): + crosstool = make_crosstool(""" + linking_mode_flags { + mode: DYNAMIC + } + """) + output = migrate_to_string(crosstool) + self.assertNotIn("linking_mode_flags", output) + self.assertNotIn("DYNAMIC", output) + self.assertIn("name: \"dynamic_linking_mode\"", output) + + def test_linking_mode_features_are_not_added_when_present(self): + crosstool = make_crosstool(""" + linking_mode_flags { + mode: DYNAMIC + linker_flag: 'dynamic-flag' + } + linking_mode_flags { + mode: FULLY_STATIC + linker_flag: 'fully-static-flag' + } + linking_mode_flags { + mode: MOSTLY_STATIC + linker_flag: 'mostly-static-flag' + } + linking_mode_flags { + mode: MOSTLY_STATIC_LIBRARIES + linker_flag: 'mostly-static-libraries-flag' + } + feature { name: "static_linking_mode" } + feature { name: "dynamic_linking_mode" } + feature { name: "static_linking_mode_nodeps_library" } + feature { name: "fully_static_link" } + """) + output = migrate_to_string(crosstool) + self.assertNotIn("linking_mode_flags", output) + self.assertNotIn("DYNAMIC", output) + self.assertNotIn("MOSTLY_STATIC", output) + self.assertNotIn("MOSTLY_STATIC_LIBRARIES", output) + self.assertNotIn("MOSTLY_STATIC_LIBRARIES", output) + self.assertNotIn("dynamic-flag", output) + self.assertNotIn("fully-static-flag", output) + self.assertNotIn("mostly-static-flag", output) + self.assertNotIn("mostly-static-libraries-flag", output) + + def test_unfiltered_compile_flags_is_not_added_when_already_present(self): + crosstool = make_crosstool(""" + unfiltered_cxx_flag: 'unfiltered-flag-1' + feature { name: 'something_else' } + feature { name: 'unfiltered_compile_flags' } + feature { name: 'something_else_2' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(output.feature[1].name, "unfiltered_compile_flags") + self.assertEqual(output.feature[1].flag_set[0].action, ALL_COMPILE_ACTIONS) + self.assertEqual(output.feature[1].flag_set[0].flag_group[0].flag, + ["unfiltered-flag-1"]) + self.assertEqual(output.feature[2].name, "something_else_2") + + def test_unfiltered_compile_flags_is_added_at_the_end(self): + crosstool = make_crosstool(""" + feature { name: 'something_else' } + unfiltered_cxx_flag: 'unfiltered-flag-1' + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(output.feature[1].name, "unfiltered_compile_flags") + self.assertEqual(output.feature[1].flag_set[0].action, ALL_COMPILE_ACTIONS) + self.assertEqual(output.feature[1].flag_set[0].flag_group[0].flag, + ["unfiltered-flag-1"]) + + def test_default_link_flags_is_added_first(self): + crosstool = make_crosstool(""" + linker_flag: 'linker-flag-1' + feature { name: 'something_else' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "default_link_flags") + self.assertEqual(output.feature[0].enabled, True) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["linker-flag-1"]) + + def test_default_link_flags_is_not_added_when_already_present(self): + crosstool = make_crosstool(""" + linker_flag: 'linker-flag-1' + feature { name: 'something_else' } + feature { name: 'default_link_flags' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(output.feature[1].name, "default_link_flags") + + def test_default_compile_flags_is_not_added_when_no_reason_to(self): + crosstool = make_crosstool(""" + feature { name: 'something_else' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(len(output.feature), 1) + + def test_default_compile_flags_is_first(self): + crosstool = make_crosstool(""" + compiler_flag: 'compiler-flag-1' + feature { name: 'something_else' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "default_compile_flags") + self.assertEqual(output.feature[0].enabled, True) + self.assertEqual(output.feature[0].flag_set[0].flag_group[0].flag, + ["compiler-flag-1"]) + + def test_default_compile_flags_not_added_when_present(self): + crosstool = make_crosstool(""" + compiler_flag: 'compiler-flag-1' + feature { name: 'something_else' } + feature { name: 'default_compile_flags' } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(output.feature[1].name, "default_compile_flags") + self.assertEqual(len(output.feature[1].flag_set), 0) + + def test_migrate_expand_if_all_available_from_flag_sets(self): + crosstool = make_crosstool(""" + action_config { + action_name: 'something' + config_name: 'something' + flag_set { + expand_if_all_available: 'foo' + flag_group { + flag: '%{foo}' + } + flag_group { + expand_if_all_available: 'bar' + flag: 'bar' + } + } + } + feature { + name: 'something_else' + flag_set { + action: 'c-compile' + expand_if_all_available: 'foo' + flag_group { + flag: '%{foo}' + } + flag_group { + expand_if_all_available: 'bar' + flag: 'bar' + } + } + } + """) + migrate_legacy_fields(crosstool) + output = crosstool.toolchain[0] + self.assertEqual(output.action_config[0].action_name, "something") + self.assertEqual(len(output.action_config[0].flag_set), 1) + self.assertEqual( + len(output.action_config[0].flag_set[0].expand_if_all_available), 0) + self.assertEqual(len(output.action_config[0].flag_set[0].flag_group), 2) + self.assertEqual( + output.action_config[0].flag_set[0].flag_group[0] + .expand_if_all_available, ["foo"]) + self.assertEqual( + output.action_config[0].flag_set[0].flag_group[1] + .expand_if_all_available, ["bar", "foo"]) + + self.assertEqual(output.feature[0].name, "something_else") + self.assertEqual(len(output.feature[0].flag_set), 1) + self.assertEqual( + len(output.feature[0].flag_set[0].expand_if_all_available), 0) + self.assertEqual(len(output.feature[0].flag_set[0].flag_group), 2) + self.assertEqual( + output.feature[0].flag_set[0].flag_group[0].expand_if_all_available, + ["foo"]) + self.assertEqual( + output.feature[0].flag_set[0].flag_group[1].expand_if_all_available, + ["bar", "foo"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/migration/legacy_fields_migrator.py b/tools/migration/legacy_fields_migrator.py new file mode 100644 index 00000000..10ebacd2 --- /dev/null +++ b/tools/migration/legacy_fields_migrator.py @@ -0,0 +1,53 @@ +"""Script migrating legacy CROSSTOOL fields into features. + +This script migrates the CROSSTOOL to use only the features to describe C++ +command lines. It is intended to be added as a last step of CROSSTOOL generation +pipeline. Since it doesn't retain comments, we assume CROSSTOOL owners will want +to migrate their pipeline manually. +""" + +# Tracking issue: https://github.com/bazelbuild/bazel/issues/5187 +# +# Since C++ rules team is working on migrating CROSSTOOL from text proto into +# Starlark, we advise CROSSTOOL owners to wait for the CROSSTOOL -> Starlark +# migrator before they invest too much time into fixing their pipeline. Tracking +# issue for the Starlark effort is +# https://github.com/bazelbuild/bazel/issues/5380. + +from absl import app +from absl import flags +from google.protobuf import text_format +from third_party.com.github.bazelbuild.bazel.src.main.protobuf import crosstool_config_pb2 +from tools.migration.legacy_fields_migration_lib import migrate_legacy_fields + +flags.DEFINE_string("input", None, "Input CROSSTOOL file to be migrated") +flags.DEFINE_string("output", None, + "Output path where to write migrated CROSSTOOL.") + + +def main(unused_argv): + crosstool = crosstool_config_pb2.CrosstoolRelease() + + input_filename = flags.FLAGS.input + output_filename = flags.FLAGS.output + + if not input_filename: + raise app.UsageError("ERROR input unspecified") + if not output_filename: + raise app.UsageError("ERROR output unspecified") + + f = open(input_filename, "r") + input_text = f.read() + text_format.Merge(input_text, crosstool) + f.close() + + output_text = migrate_legacy_fields(crosstool) + + f = open(output_filename, "w") + output_text = text_format.MessageToString(crosstool) + f.write(output_text) + f.close() + + +if __name__ == "__main__": + app.run(main)