Compartilhando biblioteca entre projetos com o TFS

Na semana passada o amigo Ricardo Dorta me mandou uma pergunta por email que é tão comum que achei que merecia virar um post.

Lembro que quando usava Source Safe (jesus!!!), ele tinha uma opção de link, que fazia com que ao modificar um arquivo em um projeto, o mesmo arquivo em outro projeto também era modificado.

Explicando o contexto, tenho algumas assemblies com classes que serão utilizadas por vários projetos, porém esses projetos tem targets diferentes (Xamarin, Unity, Windows Store, Windows Phone etc…). A solução tosca é criar todos os projetos na mesma pasta compartilhando os arquivos fisicamente. Será que o TFS me da uma solução mais “elegante”?

Em suma: Temos vários projetos de aplicações (por exemplo, uma aplicação Web e outra Windows Phone) em soluções diferentes (pois são sistemas diferentes). Entretanto, esses projetos dependem de bibliotecas comuns, que por sua vez estão em suas próprias soluções. Como lidar com isso?

O problema

Considere a estrutura de projetos abaixo. Repare que temos dois diretórios de bibliotecas (FrameworkLib1, FrameworkLib2) e dois sistemas (PhoneApp, WebApp).

Estrutura original dos projetos
244

Os dois sistemas consomem as duas bibliotecas. Ou seja, é como se a estrutura de projetos fosse, na verdade, assim:

Estrutura com as bibliotecas sob os sistemas
244

Dessa forma, os sistemas poderiam referenciar os projetos das bibliotecas. Não apenas isso, mas seria possível também editar as bibliotecas ao mesmo tempo que damos manutenção nos sistemas.

O jeito obsoleto – Share do SourceSafe

O Visual SourceSafe oferecia um recurso de compartilhamento (Share, ou “link” como meu amigo chamou). Com ele, era possível fazer exatamente o que ele gostaria: “injetar” os projetos das bibliotecas na estrutura dos sistemas.

Compartilhando pastas no SourceSafe
162

Dessa maneira, criava-se uma estrutura como a da figura a seguir. Nela, quaisquer checkins feitos nos diretórios PhoneApp/Src/PhoneApp/Lambda3.FrameworkLib1 (ou FrameworkLib2) seriam refletidos no diretório original Lambda3.FrameworkLib1/Lambda3.FrameworkLib1 (ou FrameworkLib2).

Essa técnica até resolveria o problema do meu amigo – independente de ser a melhor solução ou não – não fosse um pequeno detalhe: o TFS não tem o recurso de Share.

No TFS temos três abordagens possíveis: uma client-side, uma server-side e uma terceira independente do TFS.

Abordagem 1:  Client-side – Mapeamentos de workspace

Um recurso poderoso – e pouco explorado – do TFS é o uso do mapeamento de workspaces para baixar o código-fonte em locais específicos do nosso computador.

Normalmente, as “boas práticas” recomendam que mapeemos apenas o diretório-raiz do servidor do TFS a um diretório local em nosso computador; dessa forma, garantimos a consistência da estrutura de diretórios. Mas para resolver este caso específico, iremos contra essa boa prática. Veja o que eu fiz:

  1. Na janela Source Control Explorer, selecionei a opção Workspaces:
    Opção "Workspaces" da janela Source Control Explorer
  2. Depois, cliquei em Add para adicionar um novo workspace:
    Criando um novo workspace na caixa de diálogo Manage Workspaces
  3. Finalmente, configurei meus mapeamentos. Repare que peguei os diretórios dos projetos das minhas bibliotecas e “injetei-os” sob o diretório da minha aplicação. O resultado final é exatamente o mesmo que teríamos com o Share do SourceSafe. Qualquer alteração que eu faça nesses diretórios mapeados refletirá nos originais.
    Mapeando as bibliotecas para dentro do diretório do projeto

Prós e contras

  • Prós
    • Fácil de configurar – similar ao comando Share do SourceSafe;
    • Não requer nenhuma alteração no servidor. A configuração é feita apenas no workspace;
    • Alterações feitas nos projetos das bibliotecas são refletidas imediatamente no diretório original.
  • Contras
    • Por ser uma solução client-side, precisa ser repetida no computador de todos os desenvolvedores;
    • Como não podemos criar mais de um mapeamento por diretório, não dá para configurar PhoneApp e WebApp (nossos exemplos) ao mesmo tempo. Precisaríamos ter um workspace para cada aplicação;
    • Times não podem escolher quando recebem as alterações feitas por outros times, já que estão todos apontando para o mesmo diretório compartilhado;
    • Mapeamento deve ser replicado manualmente nas definições de build automatizado, senão não compila.

Abordagem 2: Server-sideBranches

O uso de workspaces pode ser bem simples, mas não dá para garantir que todos os desenvolvedores estarão com seus workspaces devidamente configurados. Se alguém estiver fora do padrão, estará feita a “caquinha”! Smile

Assim, uma alternativa mais “segura” seria definir os compartilhamentos do lado do servidor e não do lado do cliente. Dessa forma evitaríamos a necessidade de configurar o que quer que fosse nas máquinas dos desenvolvedores.

E como fazer isso do lado do servidor? Ora, é simples: com branches!

  1. Crie uma branch para cada biblioteca:
    image
  2. Aponte para dentro do diretório da solução dos aplicativos que irão consumir as bibliotecas:
    image
  3. Repita o processo para cada biblioteca. O resultado final será algo assim:
    image

Prós e contras

  • Prós
    • Solução server-side – não depende de configuração na máquina dos desenvolvedores;
    • Mais fácil de garantir que todos estão seguindo a mesma estrutura definida no servidor;
    • Permite o isolamento de alterações – ou seja, um time consegue decidir quando recebe as alterações feitas por outro time;
    • Transparente para a automação de build – nenhuma configuração adicional necessária.
  • Contras
    • Alterações não são propagadas imediatamente. Requer Merge para transferir alterações feitas nas cópias dos aplicativos para o diretório original das bibliotecas (e vice-versa);
    • Sujeito a perda de sincronia e excesso de conflitos se o time não se lembrar de fazer merges com frequência;
    • Mais branches para serem gerenciados – todo branch deve ser gerenciado!

Abordagem 3: Independente – Nuget

As duas soluções anteriores, apesar de funcionarem, têm suas limitações. O que mais me incomoda nelas é o processo para a sincronização das alterações feitas nas cópias das bibliotecas (injetadas dentro das solutions das aplicações) e seus diretórios originais. Na primeira solução, baseada em workspaces, não temos controle nenhum. Já na segunda, temos algum controle (pois podemos decidir o momento do merge) mas ainda assim não é um processo 100% seguro.

Por conta disso, prefiro uma solução baseada em Nuget:

  1. As bibliotecas (FrameworkLib1, FrameworkLib2) são empacotadas como pacotes Nuget (sugiro o uso do TFS Nugetter);
  2. Criamos um Feed Nuget local para hospedar os pacotes (com o Nuget Server);
  3. Os projetos deixam de referenciar diretamente os projetos das bibliotecas e passam a referenciar os pacotes Nuget.

Conclusão

Mas afinal de contas, qual é a melhor solução? Claro que isso depende muito do cenário de cada time, mas no geral eu tendo a recomendar a seguinte ordem de preferência:

image
111

A solução baseada em Nuget, apesar de ter o custo inicial mais alto (pois depende de montagem de infraestrutura) acaba sendo, ao longo do tempo, a mais robusta. Mas, como dizem os americanos, YMMV. Smile

Um abraço,
Igor

Autor: Igor Abade

Igor Abade V. Leite ([email protected]) é Microsoft MVP (Most Valuable Professional) de Visual Studio ALM desde 2006. Palestrante em diversos eventos da comunidade de desenvolvimento de software (TechEd Brasil, The Developers’ Conference, DevOps Summit Brasil, Agile Brazil, Visual Studio Summit, QCON e outros), é também autor de artigos em revistas e sites como o MSDN Brasil. Desde março de 2011 é um dos sócios da Lambda3, uma consultoria especializada em ALM, desenvolvimento de software e treinamentos. Visite seu blog sobre VS ALM em http://www.tshooter.com.br/ e siga-o no Twitter @igorabade.

4 pensamentos em “Compartilhando biblioteca entre projetos com o TFS”

  1. Olá, Igor
    Não sei se é uma boa solução, mas é possível utilizar “Gated Check-in” e definir um diretório de saída como sendo uma pasta compartilhada fixa na rede. Esta integração contínua estaria no projeto dos componentes e todos projetos que o utilizam, referenciariam os componentes do diretório compartilhado. Claro que temos problemas, como a necessidade de estar na mesma rede do servidor compartilhado, problemas com segurança e a necessidade todos os projetos utilizarem a mesma versão (provavelmente a última). Como vantagem teríamos a propagação do componente gerenciada por um proceddo de build, que pode incluir automação de testes, entre outros. No livro “Team Development with MS VSTS”, capítulo 6, é relatado sobre as soluções “Workspace Mapping” e “Branches” para este tipo de problema. Gostei da sua idéia com o Nuget e relatei neste comentário uma outra opção com “Gated Check-in”, apesar dos seus problemas.

  2. Igor,
    Você tem algum tutorial de como montar a solução baseada em Nuget? Estamos implementando o Visual Studio Team Services e gostaria de um exemplo de como fazer o controle de projetos compartilhados. Exemplo: Tenho um projeto Web para um Cliente X que usa um projeto(class library). Porem essa class library tb é utilizada pelo projeto Y.

    1. Leonardo, não tenho nenhum exemplo à mão, mas é uma excelente ideia para um novo post. Fique ligado! 😉

Deixe seu comentário!