Implement `gn edit delete` and `gn edit remove attr` Change-Id: Ib41ef3fa55e622cecd6fea3e748305ef6a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/25400 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Matt Stark <msta@google.com>
diff --git a/docs/reference.md b/docs/reference.md index 8053e94..671d3d0 100644 --- a/docs/reference.md +++ b/docs/reference.md
@@ -728,19 +728,27 @@ #### **Commands**: ``` + delete + Deletes the matched targets entirely. + + Example: + gn edit "delete" //src/tools:old_target + + remove <attribute> + Removes <attribute> entirely. + + Example: + gn edit "remove testonly" //src/tools:* + set <attribute>[:list] <value(s)> Sets or overwrites the target's <attribute> to <value(s)>. If multiple values are provided, or if the ":list" suffix is appended to the attribute, <value(s)> is interpreted as a list. -``` -#### **Examples**: -``` - gn edit "set testonly true" //src/tools:* - Sets 'testonly' to 'true' for all targets in - `//src/tools/BUILD.gn`. - gn edit "set srcs:list foo.cc foo.h" //:foo - Sets 'srcs' to '[ "foo.cc", "foo.h" ]' for //:foo. + Examples: + gn edit "set testonly true" //src/tools:* + gn edit "set srcs:list foo.cc" //:foo + gn edit "set deps :bar :baz" //:foo ``` ### <a name="cmd_format"></a>**gn format [\--dump-tree] [\--format-width=WIDTH] (\--stdin | <list of build_files...>)** [Back to Top](#gn-reference)
diff --git a/src/gn/build_file_editor.cc b/src/gn/build_file_editor.cc index 01a346d..e784edc 100644 --- a/src/gn/build_file_editor.cc +++ b/src/gn/build_file_editor.cc
@@ -89,7 +89,8 @@ } // namespace bool TreeNode::is_conditional() const { - for (auto it = stack_.rbegin(); it != stack_.rend(); ++it) { + DCHECK(!stack_.empty()) << "stack should never be empty"; + for (auto it = stack_.rbegin() + 1; it != stack_.rend(); ++it) { if ((*it)->AsCondition()) { return true; }
diff --git a/src/gn/edit_command.cc b/src/gn/edit_command.cc index b8095ff..2ca0d3d 100644 --- a/src/gn/edit_command.cc +++ b/src/gn/edit_command.cc
@@ -35,17 +35,27 @@ " in your build files instructing you what to do.\n" "\n" "Commands:\n" + " delete\n" + " Deletes the matched targets entirely.\n" + "\n" + " Example:\n" + " gn edit \"delete\" //src/tools:old_target\n" + "\n" + " remove <attribute>\n" + " Removes <attribute> entirely.\n" + "\n" + " Example:\n" + " gn edit \"remove testonly\" //src/tools:*\n" + "\n" " set <attribute>[:list] <value(s)>\n" " Sets or overwrites the target's <attribute> to <value(s)>.\n" " If multiple values are provided, or if the \":list\" suffix is\n" " appended to the attribute, <value(s)> is interpreted as a list.\n" "\n" - "Examples:\n" - " gn edit \"set testonly true\" //src/tools:*\n" - " Sets 'testonly' to 'true' for all targets in\n" - " `//src/tools/BUILD.gn`.\n" - " gn edit \"set srcs:list foo.cc foo.h\" //:foo\n" - " Sets 'srcs' to '[ \"foo.cc\", \"foo.h\" ]' for //:foo.\n"; + " Examples:\n" + " gn edit \"set testonly true\" //src/tools:*\n" + " gn edit \"set srcs:list foo.cc\" //:foo\n" + " gn edit \"set deps :bar :baz\" //:foo\n"; Result<std::pair<std::vector<SourceFile>, EditState>> RunEditImpl( const std::vector<std::string>& args,
diff --git a/src/gn/edit_command_unittest.cc b/src/gn/edit_command_unittest.cc index 1961e56..cea3a60 100644 --- a/src/gn/edit_command_unittest.cc +++ b/src/gn/edit_command_unittest.cc
@@ -125,6 +125,84 @@ "Target(s) not found: //:nonexistent"); } +TEST_F(EditCommandTest, DeleteSubcommand) { + EXPECT_SUCCESS(DoEdit("delete", {"//:bar"}, + R"( +executable("foo") { + sources = [ "foo.cc" ] +} +executable("bar") { + sources = [ "bar.cc" ] +} +)"), + Edited(R"( +executable("foo") { + sources = [ "foo.cc" ] +} +)")); + + EXPECT_SUCCESS(DoEdit("delete", {"//:bar"}, + R"( +if (is_win) { + executable("bar") { + sources = [ "bar.cc" ] + } +} else { + executable("bar") { + sources = [ "bar.cc" ] + } +} +)"), + Edited(R"( +if (is_win) { + # TODO(gn edit: delete): + # This would normally be deleted but is conditional. + # Manual intervention is required to decide whether it should actually be deleted. + executable("bar") { + sources = [ "bar.cc" ] + } +} else { + # TODO(gn edit: delete): + # This would normally be deleted but is conditional. + # Manual intervention is required to decide whether it should actually be deleted. + executable("bar") { + sources = [ "bar.cc" ] + } +} +)", + EditState({Label(SourceDir("//"), "bar")}))); +} + +TEST_F(EditCommandTest, RemoveAttributeSubcommand) { + EXPECT_SUCCESS(DoEdit("remove testonly", + R"( +executable("foo") { + sources = [ "foo.cc" ] + testonly = true +} +)"), + Edited(R"( +executable("foo") { + sources = [ "foo.cc" ] +} +)")); + + EXPECT_SUCCESS( + DoEdit("remove nonexistent_attribute", {"//:foo"}, + R"( +executable("foo") { +} +)"), + Edited(R"( +executable("foo") { +} +)", + EditState{{}, + {Err(Location(), + "Target \"//:foo\" does not contain the " + "attribute \"nonexistent_attribute\".")}})); +} + TEST_F(EditCommandTest, SetSubcommand) { // New bool attribute EXPECT_SUCCESS(DoEdit("set testonly true",
diff --git a/src/gn/edit_subcommands.cc b/src/gn/edit_subcommands.cc index 243cff5..31c18f5 100644 --- a/src/gn/edit_subcommands.cc +++ b/src/gn/edit_subcommands.cc
@@ -65,6 +65,30 @@ }; } +EditCommand DeleteCommand() { + return EditTargetCommand([](BuildFile& build_file, const EditTarget& target, + EditState& state) -> Err { + target.node.RemoveSelf(state, target); + return Ok(); + }); +} + +EditCommand RemoveAttributeCommand(std::string attribute) { + return EditTargetCommand([attribute = std::move(attribute)]( + BuildFile& build_file, const EditTarget& target, + EditState& state) -> Err { + auto assignments = target.assignments(attribute); + for (auto& assignment : assignments) { + assignment.RemoveSelf(state, target); + } + if (assignments.empty() && target.is_explicit) { + target.add_warning( + state, "does not contain the attribute \"" + attribute + "\"."); + } + return Ok(); + }); +} + // Sets an attribute to a value. EditCommand SetCommand(std::string attribute, Value value) { return EditTargetCommand([=](BuildFile& build_file, const EditTarget& target, @@ -95,7 +119,18 @@ return Err(Location(), "Empty command."); } - if (args[0] == "set") { + if (args[0] == "delete") { + if (args.size() != 1) { + return Err(Location(), "Invalid delete command.", "Usage: delete"); + } + return DeleteCommand(); + } else if (args[0] == "remove") { + if (args.size() != 2) { + return Err(Location(), "Invalid remove command.", + "Usage: remove <attribute>"); + } + return RemoveAttributeCommand(args[1]); + } else if (args[0] == "set") { if (args.size() < 3) { return Err(Location(), "Invalid set command: missing attribute or value.\n" @@ -121,10 +156,10 @@ } return SetCommand(std::string(attribute), std::move(val)); + } else { + return Err(Location(), + "Unknown edit command: " + std::string(args[0]) + + "\n" + "See `gn help edit` for list of supported commands."); } - - return Err(Location(), - "Unknown edit command: " + std::string(args[0]) + - "\n" - "See `gn help edit` for list of supported commands."); }